# coding=UTF-8
"""
Copyright Ian Ross Williams, 2012

    This file is part of ArcSecond.

    ArcSecond is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ArcSecond is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ArcSecond.  If not, see <http://www.gnu.org/licenses/>.
"""

import pygtk
pygtk.require('2.0')
import gtk.gdk
import gobject
import numpy as np
import Image, ImageFilter, ImageOps,ImageChops
import time
import thread
import os
from arcsecond import filters

class notebook_register():
    frameEnabled=[]         #Whether a frame is enabled or not
    frameOffset=[]          #The x and y shift of the registered frame
    frameScore=[]           #A measure of frame quality for each frame
    ROI=None
    medianFilterSize=3
    medianFilterEnabled=0
    waveletThreshold=1
    waveletDenoiseEnabled=0
    destripeFilterEnabled=False
    darkEnabled=False
    darkFrameFile=None
    artificialDarkEnabled=False
    cropEnabled=False
    upsampleEnabled=False
    debayerMethod=0
    normaliseEnabled=False
    
    notebook = None
    notebook_view_stack = None
    alive=True
    process_thread_alive=False
    ui_enabled=True
    selectedFlag=False
        
    #Column numbers for registration list
    (
    COLUMN_ENABLED,
    COLUMN_NAME,
    COLUMN_XSHIFT,
    COLUMN_YSHIFT,
    COLUMN_SCORE
    ) = range(5)

    (METHOD_NONE,
     METHOD_FFT,
     METHOD_CANNY_FFT,
     METHOD_GEOMETRIC_MEDIAN) = range(4)
    method = METHOD_NONE
         
    (SCORE_NONE,
     SCORE_ENTROPY,
     SCORE_SNR,
     SCORE_GRADIENT,
     SCORE_VALUEGRADIENT,
     SCORE_STDDEV,
     SCORE_CORRELATION) = range(7)
    scoreMethod = METHOD_NONE

    def __init__(self, notebook, notebook_view_stack, statusbar,id, notebook_page_view):
        self.notebook = notebook
        self.notebook_view_stack = notebook_view_stack
        self.statusbarLeft=statusbar
        self.statusbarLeftID=id
        self.PAGEVIEW=notebook_page_view
    
    def __del__(self):
        self.buttonStop_callback()
        
    def buttonStop_callback(self, button=None):
        self.alive=False
        while self.process_thread_alive: time.sleep(0.1)
        self.buttonStop.set_sensitive(False)
        
    def buttonAdd_callback(self, button):
        dialog = gtk.FileChooserDialog("Open..",
                                       None,
                                       gtk.FILE_CHOOSER_ACTION_OPEN,
                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)
        dialog.set_select_multiple(True)
        filter = gtk.FileFilter()
        filter.set_name("Images")
        filter.add_mime_type("image/png")
        filter.add_mime_type("image/ppm")
        filter.add_mime_type("image/pnm")
        filter.add_mime_type("image/jpeg")
        filter.add_mime_type("image/gif")
        filter.add_mime_type("image/bmp")
        filter.add_mime_type("image/psd")
        filter.add_pattern("*.png")
        filter.add_pattern("*.jpg")
        filter.add_pattern("*.gif")
        filter.add_pattern("*.bmp")
        filter.add_pattern("*.xbm")
        filter.add_pattern("*.tif")
        filter.add_pattern("*.xpm")
        filter.add_pattern("*.psd")
        dialog.add_filter(filter)
        filter = gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        dialog.add_filter(filter)
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            filelist = dialog.get_filenames()
            i0 = len(self.frameEnabled)
            for i,frame in enumerate(filelist):
                self.ui_switch(True)
                self.frameEnabled.append(True)
                self.frameOffset.append((0.,0.))
                self.frameScore.append(0.)
                lsRow = self.listStoreRegister.append()
                self.listStoreRegister.set(lsRow,
                    self.COLUMN_ENABLED, self.frameEnabled[i+i0],
                    self.COLUMN_NAME, frame,
                    self.COLUMN_XSHIFT, self.frameOffset[i+i0][0],
                    self.COLUMN_YSHIFT, self.frameOffset[i+i0][1],
                    self.COLUMN_SCORE , self.frameScore[i+i0])
            
        dialog.destroy()
        self.select_all()
         
    def buttonRemove_callback(self, button):
        selection = self.treeview.get_selection()
        model, selected = selection.get_selected_rows()
        iters = [model.get_iter(path) for path in selected]
        for iter in iters:
            model.remove(iter)
        
        if not len(self.listStoreRegister):
            self.ui_switch(False)
            
    def combobox_method_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.method = active

    def combobox_score_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.scoreMethod = active

    def combobox_median_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.medianFilterEnabled = active
        self.spinbuttonMedian.set_sensitive(self.medianFilterEnabled>0)

    def combobox_wavelet_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.waveletDenoiseEnabled = active
        self.spinbuttonWaveletThreshold.set_sensitive(self.waveletDenoiseEnabled>0)
        
    def combobox_debayer_changed(self,cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.debayerMethod = active
    
    def upsample_toggled(self, button):
        self.upsampleEnabled = not self.upsampleEnabled
        
    def normalise_toggled(self, button):
        self.normaliseEnabled = not self.normaliseEnabled

    def median_size_changed(self,rng):
        self.medianFilterSize = int(rng.get_value())
            
    def wavelet_threshold_changed(self,rng):
        self.waveletThreshold = float(rng.get_value())

    def destripe_toggled(self,button):
        self.destripeFilterEnabled = not self.destripeFilterEnabled
        
    def dark_toggled(self,button):
        self.darkEnabled=not self.darkEnabled
        if self.darkEnabled: 
            self.load_darkframe()
            if not self.darkFrameFile:
                self.checkboxDark.set_active(False)
        else:
            self.darkFrameFile=None

    def artificial_dark_toggled(self,button):
        self.artificialDarkEnabled = not self.artificialDarkEnabled
    
    def selected(self, ROI):
        self.ROI = ROI
        if self.ROI==None: 
            self.clear_ROI()
        else:
            self.selectedFlag=True
            self.checkboxROI.set_active(True)
            self.selectedFlag=False
            self.checkboxCrop.set_sensitive(True)
    
        self.select_all()
        if self.listStoreRegister.get_iter_first() is None:
            self.buttonAdd.grab_default()
            self.buttonAdd.grab_focus()
        else:
            self.buttonRegister.grab_default()
            self.buttonRegister.grab_focus()
        
    def clear_ROI(self):
        self.checkboxROI.set_active(False)
        
    def ROI_toggled(self,button):
        if self.selectedFlag: return
        if self.ROI==None and len(self.listStoreRegister)>0:
            self.notebook_view_stack.clear_ROI()
            self.notebook_view_stack.registered = False
            self.statusbarLeft.push(self.statusbarLeftID, "Select the region of interest by clicking and dragging.")
            self.notebook.set_current_page(self.PAGEVIEW)
        else:
            self.ROI=None
            self.notebook_view_stack.clear_ROI()
            self.checkboxCrop.set_active(False)
            self.checkboxCrop.set_sensitive(False)
            self.statusbarLeft.push(self.statusbarLeftID, "Cleared the Region of Interest")
            
    def crop_toggled(self, button):
        self.cropEnabled = not self.cropEnabled
        
    def registration_enabled_toggled(self,widget,path,model):
        row=int(path)
        model[row][0] = not model[row][0]
        self.frameEnabled[row] = model[row][0]
    
    def buttonRegister_callback(self, button):
        #Start a thread to register the frames
        if not self.process_thread_alive:
            thread.start_new_thread(self.process_thread,(0,))
            #self.process_thread()
        
    def process_thread(self,args=None):
        self.buttonStop.set_sensitive(True)
        self.alive=True
        self.process_thread_alive=True
        selection = self.treeview.get_selection()
        model, selected = selection.get_selected_rows()
        iters = [model.get_iter(path) for path in selected]
        ref=None
        nimFirst=None
        
        if self.darkEnabled and self.darkFrameFile is not None:
            try:
                self.darkFrame = Image.open(self.darkFrameFile)
            except ValueError:
                md = gtk.MessageDialog(
                                       None, 
                                       0, gtk.MESSAGE_ERROR, 
                                       gtk.BUTTONS_CLOSE, "The selected dark-frame is not a recognised image format.")
                md.run()
                md.destroy()
                self.darkFrame = None
                self.checkboxDark.toggled()
            except AttributeError:
                self.darkFrame = None
                self.checkboxDark.toggled()

        if self.artificialDarkEnabled:
            artificialDarkMode=""
            for iter in iters:
                if not self.alive: 
                    self.process_thread_alive=False
                    with gtk.gdk.lock:     #Protect gtk from non-thread safe call
                        self.buttonStop.set_sensitive(False)
                    return
                
                if model.get_value(iter,self.COLUMN_ENABLED):
                    imageFile=model.get_value(iter, self.COLUMN_NAME)
                    with gtk.gdk.lock:
                        self.statusbarLeft.push(self.statusbarLeftID,"Making dark-frame: %s" % imageFile)
                    try:
                        im = Image.open(imageFile)
                    except IOError:
                        with gtk.gdk.lock:
                            model.set_value(iter,self.COLUMN_ENABLED,False)
                        self.frameEnabled = False
                        continue

                nim = np.asarray(im)

                if artificialDarkMode=="": 
                    artificialDarkMode=im.mode
                    artificialDarkFrame=np.array(nim)
                    
                elif im.mode==artificialDarkMode:
                    if len(im.mode)==1:
                        stack=np.dstack(([artificialDarkFrame],[nim]))
                        artificialDarkFrame = np.min(stack,axis=0)
                    else:
                        for chNo in range(3):
                            stack=np.dstack( (artificialDarkFrame[:,:,chNo],nim[:,:,chNo]) )
                            artificialDarkFrame[:,:,chNo] = np.min(stack,axis=2)
            
            if len(im.mode)==1:
                self.artificialDarkFrame = Image.fromarray(artificialDarkFrame,im.mode)
            else:
                R = Image.fromarray(artificialDarkFrame[:,:,0],"L")
                G = Image.fromarray(artificialDarkFrame[:,:,1],"L")
                B = Image.fromarray(artificialDarkFrame[:,:,2],"L")
                self.artificialDarkFrame = Image.merge(im.mode,(R,G,B))
                                 
        for iter in iters:
            if not self.alive: 
                self.process_thread_alive=False
                with gtk.gdk.lock:     #Protect gtk from non-thread safe call
                    self.buttonStop.set_sensitive(False)
                return
            
            if model.get_value(iter,self.COLUMN_ENABLED):
                imageFile=model.get_value(iter, self.COLUMN_NAME)
                with gtk.gdk.lock:
                    self.statusbarLeft.push(self.statusbarLeftID,imageFile)
                try:
                    im = Image.open(imageFile)
                except IOError:
                    with gtk.gdk.lock:
                        model.set_value(iter,self.COLUMN_ENABLED,False)
                    self.frameEnabled = False
                    continue
                
                doneSomething=False

                #Pre-process
                if self.darkEnabled and self.darkFrame is not None:
                    doneSomething=True
                    im=ImageChops.subtract(im,self.darkFrame)
                elif self.artificialDarkEnabled:
                    doneSomething=True
                    im=ImageChops.subtract(im,self.artificialDarkFrame)

                if self.cropEnabled:
                    doneSomething=True
                    cropRegion = np.array(self.ROI)
                    #Ensure region is cropped to an even position so that debayer method still works
                    cropRegion[0] = cropRegion[0] + (cropRegion[0]%2)
                    cropRegion[1] = cropRegion[1] + (cropRegion[1]%2)
                    cropRegion[2] = cropRegion[2] - (cropRegion[2]%2)
                    cropRegion[3] = cropRegion[3] - (cropRegion[3]%2)

                    im = im.crop(np.array(cropRegion,dtype=np.int))

                if self.debayerMethod>0:
                    doneSomething=True
                    im=filters.demosaic(im,self.debayerMethod)
                    
                if self.normaliseEnabled:
                    doneSomething=True
                    im=ImageOps.autocontrast(im)
                    
                if self.destripeFilterEnabled:
                    doneSomething=True
                    tileSize = np.min(im.size)**0.65
                    im=filters.destripe(im,tileSize,99)
                
                if self.medianFilterEnabled==2:
                    doneSomething=True
                    im=im.filter(ImageFilter.MedianFilter(self.medianFilterSize))

                if self.waveletDenoiseEnabled==2:
                    doneSomething=True
                    im=filters.waveletDenoisePIL(im,self.waveletThreshold)
                
                #Temporary filters
                imm=im
                if self.ROI and not self.cropEnabled:
                    imm = imm.crop(np.array(self.ROI,dtype=np.int))

                if self.medianFilterEnabled==1:
                    imm=imm.filter(ImageFilter.MedianFilter(self.medianFilterSize))

                if self.waveletDenoiseEnabled==1:
                    imm=filters.waveletDenoisePIL(imm,self.waveletThreshold)
                                
                #Upsample before registration
                if self.upsampleEnabled:
                    doneSomething=True
                    newsize = (imm.size[0]*2, imm.size[1]*2)
                    im  = im.resize(newsize,Image.BICUBIC)
                    imm = imm.resize(newsize,Image.BICUBIC)
                    
                #Convert the image to a grey-scale array
                nim = np.asarray(imm.convert("F")).astype(np.float32)
                if len(nim.shape)>2: nim = np.mean(nim,axis=2)
                if nimFirst==None: nimFirst=np.array(nim)
                
                #Scoring methods                
                if self.scoreMethod==self.SCORE_ENTROPY:
                    score = -np.mean(nim[nim>0.]*np.log(nim[nim>0.]))
                elif self.scoreMethod==self.SCORE_STDDEV:
                    score = np.std(nim)
                elif self.scoreMethod==self.SCORE_SNR:
                    score = np.mean(nim)/np.std(nim)
                elif self.scoreMethod==self.SCORE_GRADIENT:
                    score = np.mean((np.array(np.gradient(nim)))**2)
                elif self.scoreMethod==self.SCORE_VALUEGRADIENT:
                    score = np.mean((nim*np.gradient(nim))**2)
                elif self.scoreMethod==self.SCORE_CORRELATION:
                    score = np.mean(filters.correlate(nim,nimFirst))
                    
                if self.scoreMethod!=self.SCORE_NONE:
                    with gtk.gdk.lock:
                        model.set_value(iter, self.COLUMN_SCORE, score)

                #Registration methods
                if self.method==self.METHOD_CANNY_FFT:
                    doneSomething=True
                    nim = filters.canny(nim, sigma=3)
                
                if self.method==self.METHOD_FFT or self.method==self.METHOD_CANNY_FFT:
                    doneSomething=True
                    if ref==None:
                        imout = im
                        ref = np.fft.fft2(nim)
                        with gtk.gdk.lock:
                            model.set_value(iter,self.COLUMN_XSHIFT,0.)
                            model.set_value(iter,self.COLUMN_YSHIFT,0.)
                    else:
                        fft = np.fft.fft2(nim,s=ref.shape)
                        yshift,xshift = self.registration_dft(ref, fft)
                        with gtk.gdk.lock:
                            model.set_value(iter,self.COLUMN_XSHIFT,xshift)
                            model.set_value(iter,self.COLUMN_YSHIFT,yshift)
                            self.treeview.queue_draw()
                        imout = Image.new(im.mode,im.size,"black")
                        imout.paste(im,(xshift,yshift))

                elif self.method==self.METHOD_GEOMETRIC_MEDIAN:
                    doneSomething=True
                    if ref==None:
                        imout = im
                        ref = nim.copy()
                        xref,yref = filters.geometric_median(ref,threshold=0.8*np.max(ref))
                    else:
                        mx,my = filters.geometric_median(nim,threshold=0.8*np.max(nim))
                        xshift,yshift = int(mx-xref), int(my-yref)
                        with gtk.gdk.lock:
                            model.set_value(iter,self.COLUMN_XSHIFT,xshift)
                            model.set_value(iter,self.COLUMN_YSHIFT,yshift)
                            self.treeview.queue_draw()
                        imout = Image.new(im.mode,im.size,"black")
                        imout.paste(im,(xshift,yshift))
                else:
                    imout=im
                        
                #Save the result into a sub-directory
                if doneSomething:
                    path = os.path.dirname(imageFile) + "/registered"
                    if not os.path.exists(path):
                        os.mkdir(path)
                    registeredFile=path + "/" + os.path.basename(imageFile)
                    imout.save(registeredFile)

        self.process_thread_alive=False
        self.buttonStop.set_sensitive(False)
    
    def treeview_row_activated(self,treeview,path,view_column, model):
        iter = model.get_iter(path)
        imageFile = model.get_value(iter,1)
        frameList=self.get_frame_list()
        try:
            frameNo = frameList.index(imageFile)
        except ValueError:
            pass
        else:
            self.notebook_view_stack.set_frames(frameList)
            self.notebook_view_stack.select_frame(frameNo+1)
            self.notebook.set_current_page(3)

    def add_frames(self, framesCaptured):
        #Populate the image list
        for i,frame in enumerate(framesCaptured):
            iter = self.listStoreRegister.append()
            self.listStoreRegister.set(iter,
                self.COLUMN_ENABLED, True,
                self.COLUMN_NAME, frame,
                self.COLUMN_XSHIFT, 0.,
                self.COLUMN_YSHIFT, 0.)
            self.ui_switch(True)
            
        self.buttonRegister.grab_default()
        self.buttonRegister.grab_focus()
    
    def select_all(self):
        treeselection = self.treeview.get_selection()
        treeselection.select_all()

    def get_frame_list(self):
        frameList=[]
        scoreList=[]
        for row in self.listStoreRegister:
            frameList.append(row[self.COLUMN_NAME])        
        frameList=np.array(frameList)
        return frameList
    
    def get_frame_enabled(self):
        frameEnabled=[]
        for row in self.listStoreRegister:
            frameEnabled.append(row[self.COLUMN_ENABLED])        
        return frameEnabled
        
    def get_frame_shifts(self):
        frameShifts=[]
        for row in self.listStoreRegister:
            xs = row[self.COLUMN_XSHIFT]
            ys = row[self.COLUMN_YSHIFT]
            frameShifts.append( (xs,ys) )
        return frameShifts
    
    def registration_dft(self,buf1ft,buf2ft):    
        (m,n)=buf1ft.shape
        CC = np.abs(np.fft.ifft2(buf1ft * np.conj(buf2ft)))
        (rloc,cloc) = np.unravel_index(CC.argmax(), CC.shape)
        md2 = int(m/2) 
        nd2 = int(n/2)
        if rloc > md2:
            row_shift = rloc - m 
        else:
            row_shift = rloc 
        
        if cloc > nd2:
            col_shift = cloc - n 
        else:
            col_shift = cloc
        
        return [row_shift,col_shift] 
        
    def load_darkframe(self):
        darkDlg = gtk.FileChooserDialog(
            title="Select a dark frame",
            action=gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        darkDlg.set_default_response(gtk.RESPONSE_CANCEL)
        filter = gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        darkDlg.add_filter(filter)
        filter = gtk.FileFilter()
        filter.set_name("Images")
        filter.add_mime_type("image/png")
        filter.add_mime_type("image/ppm")
        filter.add_mime_type("image/jpeg")
        filter.add_mime_type("image/gif")
        filter.add_mime_type("image/bmp")
        filter.add_mime_type("image/psd")
        filter.add_pattern("*.png")
        filter.add_pattern("*.jpg")
        filter.add_pattern("*.gif")
        filter.add_pattern("*.bmp")
        filter.add_pattern("*.xbm")
        filter.add_pattern("*.tif")
        filter.add_pattern("*.xpm")
        filter.add_pattern("*.psd")
        darkDlg.add_filter(filter)
        response = darkDlg.run()
        if response == gtk.RESPONSE_OK:
            filename = darkDlg.get_filename()                
        else:
            filename = None
        
        darkDlg.destroy()
        self.darkFrameFile = filename

    def ui_switch(self,enabled):
        if self.ui_enabled!=enabled:
            self.ui_enabled=enabled
            self.comboboxMedian.set_sensitive(enabled)
            self.spinbuttonMedian.set_sensitive(enabled)
            self.comboboxWaveletDenoise.set_sensitive(enabled)
            self.spinbuttonWaveletThreshold.set_sensitive(enabled)
            self.checkboxDestripe.set_sensitive(enabled)
            self.checkboxROI.set_sensitive(enabled)
            self.checkboxCrop.set_sensitive(enabled and self.ROI!=None)
            self.checkbox2x.set_sensitive(enabled)
            self.comboboxDebayer.set_sensitive(enabled)
            self.checkboxDark.set_sensitive(enabled)
            self.comboboxMethod.set_sensitive(enabled)
            self.comboboxScore.set_sensitive(enabled)
            self.checkboxNormalise.set_sensitive(enabled)
            self.checkboxArtificialDark.set_sensitive(enabled)
            
    def get_page(self):
        vbox = gtk.VBox()
        
        #Add the add/remove buttons to the top of the page inside a hbox
        hboxTop = gtk.HBox()
        vbox.pack_start(hboxTop,False,False,0)
        self.buttonAdd = gtk.Button("Add...")
        self.buttonAdd.set_flags(gtk.CAN_DEFAULT)
        self.buttonAdd.connect("clicked", self.buttonAdd_callback)
        hboxTop.pack_start(self.buttonAdd, False,False,0)
        self.buttonRemove = gtk.Button("Remove")
        self.buttonRemove.connect("clicked", self.buttonRemove_callback)
        hboxTop.pack_start(self.buttonRemove,False,False,0)
        
        # Create a scrolled window to hold the image list
        sw = gtk.ScrolledWindow()
        sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vbox.pack_start(sw,True,True,0)

        self.listStoreRegister = gtk.ListStore(
            gobject.TYPE_BOOLEAN,
            gobject.TYPE_STRING,
            gobject.TYPE_FLOAT,
            gobject.TYPE_FLOAT,
            gobject.TYPE_FLOAT)

        self.treeview = gtk.TreeView(self.listStoreRegister)
        self.treeview.set_rules_hint(True)
        self.treeview.set_search_column(self.COLUMN_NAME)
        model=self.treeview.get_model()
        self.treeview.connect("row-activated",self.treeview_row_activated, model)
        sw.add(self.treeview)

        #===Add the columns to the list box===
        #'Enabled' column
        renderer = gtk.CellRendererToggle()
        column = gtk.TreeViewColumn('Enabled', renderer, active=self.COLUMN_ENABLED)
        column.set_max_width(100)
        renderer.connect('toggled', self.registration_enabled_toggled, model)
        self.treeview.append_column(column)
        #'File' column

        renderer = gtk.CellRendererText()
        #renderer.props.wrap_width = 400
        #renderer.props.wrap_mode = gtk.WRAP_CHAR
        column = gtk.TreeViewColumn('File', renderer,
                                    text=self.COLUMN_NAME)
        column.set_sort_column_id(self.COLUMN_NAME)
        column.set_expand(True)
        self.treeview.append_column(column)
        #'X-offset' column
        column = gtk.TreeViewColumn('X Offset', gtk.CellRendererText(),
                                    text=self.COLUMN_XSHIFT)
        column.set_sort_column_id(self.COLUMN_XSHIFT)
        self.treeview.append_column(column)
        #'Y-offset' column
        column = gtk.TreeViewColumn('Y Offset', gtk.CellRendererText(),
                                    text=self.COLUMN_YSHIFT)
        column.set_sort_column_id(self.COLUMN_YSHIFT)
        self.treeview.append_column(column)
        #'Score' column
        column = gtk.TreeViewColumn('Score', gtk.CellRendererText(),
                                    text=self.COLUMN_SCORE)
        column.set_sort_column_id(self.COLUMN_SCORE)
        self.treeview.append_column(column)
        
        #Allow multiple selections
        treeselection = self.treeview.get_selection()
        treeselection.set_mode(gtk.SELECTION_MULTIPLE)
        self.treeview.set_rubber_banding(True)
        
        #Add the action buttons to the end of the page inside a hbox
        swBottom = gtk.ScrolledWindow()
        swBottom.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        swBottom.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)

        hboxBottom = gtk.HBox()
        swBottom.add_with_viewport(hboxBottom)
        vbox.pack_end(swBottom,False,False,0)

        #---Add a check-button and spin-button to apply median pre-filtering---
        frame = gtk.Frame("Median filter (size):")
        vboxMedian = gtk.VBox()
        self.comboboxMedian = gtk.combo_box_new_text()
        vboxMedian.pack_start(self.comboboxMedian, expand=False, fill=False, padding=0)
        self.comboboxMedian.append_text("Disabled")
        self.comboboxMedian.append_text("Pre-filter")
        self.comboboxMedian.append_text("Permenent")
        self.comboboxMedian.set_active(0)
        self.comboboxMedian.connect("changed", self.combobox_median_changed)
       
        self.adjustmentMedian = gtk.Adjustment(value=self.medianFilterSize, lower=3, upper=10, step_incr=1, page_incr=1)
        self.spinbuttonMedian = gtk.SpinButton(self.adjustmentMedian)
        self.spinbuttonMedian.set_digits(0)
        self.spinbuttonMedian.connect("value-changed",self.median_size_changed)
        self.spinbuttonMedian.set_sensitive(self.medianFilterEnabled>0)
        vboxMedian.pack_start(self.spinbuttonMedian, expand=False, fill=False, padding=0)
        frame.add(vboxMedian)
        hboxBottom.pack_start(frame, expand=False, fill=False, padding=0)
        
        #---Add a check-button and spin-button to apply wavelet threshold filtering---
        frame = gtk.Frame("Wavelet threshold:")
        vboxWaveletDenoise = gtk.VBox()
        self.comboboxWaveletDenoise = gtk.combo_box_new_text()
        vboxWaveletDenoise.pack_start(self.comboboxWaveletDenoise, expand=False, fill=False, padding=0)
        self.comboboxWaveletDenoise.append_text("Disabled")
        self.comboboxWaveletDenoise.append_text("Pre-filter")
        self.comboboxWaveletDenoise.append_text("Permenent")
        self.comboboxWaveletDenoise.set_active(0)
        self.comboboxWaveletDenoise.connect("changed", self.combobox_wavelet_changed)
        
        self.adjustmentWaveletThreshold = gtk.Adjustment(value=self.waveletThreshold, lower=0, upper=10, step_incr=1, page_incr=1)
        self.spinbuttonWaveletThreshold = gtk.SpinButton(self.adjustmentWaveletThreshold)
        self.spinbuttonWaveletThreshold.set_digits(0)
        self.spinbuttonWaveletThreshold.connect("value-changed",self.wavelet_threshold_changed)
        self.spinbuttonWaveletThreshold.set_sensitive(self.waveletDenoiseEnabled>0)
        vboxWaveletDenoise.pack_start(self.spinbuttonWaveletThreshold, expand=False, fill=False, padding=0)
        frame.add(vboxWaveletDenoise)
        hboxBottom.pack_start(frame, expand=False, fill=False, padding=0)

        vboxROI=gtk.VBox()
        hboxBottom.pack_start(vboxROI, expand=False, fill=False, padding=0)
        self.checkboxROI = gtk.CheckButton("ROI")
        self.checkboxROI.set_active(self.ROI!=None)
        self.checkboxROI.connect("toggled",self.ROI_toggled)
        vboxROI.pack_start(self.checkboxROI, expand=False, fill=False, padding=0)

        self.checkboxCrop = gtk.CheckButton("Crop to ROI")
        self.checkboxCrop.set_active(False)
        self.checkboxCrop.connect("toggled",self.crop_toggled)
        vboxROI.pack_start(self.checkboxCrop, expand=False, fill=False, padding=0)

        self.checkboxDestripe = gtk.CheckButton("Destripe")
        self.checkboxDestripe.set_active(self.destripeFilterEnabled)
        self.checkboxDestripe.connect("toggled",self.destripe_toggled)
        vboxROI.pack_start(self.checkboxDestripe, expand=False, fill=False, padding=0)

        vboxDarkFrame = gtk.VBox()
        hboxBottom.pack_start(vboxDarkFrame, expand=False, fill=False, padding=0)
        self.checkboxDark = gtk.CheckButton("Dark frame")
        self.checkboxDark.set_active(self.darkEnabled)
        self.checkboxDark.connect("toggled",self.dark_toggled)
        vboxDarkFrame.pack_start(self.checkboxDark,expand=False,fill=False,padding=0)

        self.checkboxArtificialDark = gtk.CheckButton("Minimal Dark")
        self.checkboxArtificialDark.set_active(self.artificialDarkEnabled)
        self.checkboxArtificialDark.connect("toggled",self.artificial_dark_toggled)
        vboxDarkFrame.pack_start(self.checkboxArtificialDark,expand=False,fill=False,padding=0)

        self.checkbox2x = gtk.CheckButton("2x")
        self.checkbox2x.set_active(self.darkEnabled)
        self.checkbox2x.connect("toggled",self.upsample_toggled)
        vboxDarkFrame.pack_start(self.checkbox2x,expand=False,fill=False,padding=0)
        
        self.checkboxNormalise = gtk.CheckButton("Normalise")
        self.checkboxNormalise.set_active(self.normaliseEnabled)
        self.checkboxNormalise.connect("toggled",self.normalise_toggled)
        vboxDarkFrame.pack_start(self.checkboxNormalise,expand=False,fill=False,padding=0)

        vboxDemosaic = gtk.VBox()
        hboxBottom.pack_start(vboxDemosaic, expand=False, fill=False, padding=0)

        frame = gtk.Frame("Demosaic:")
        frameVBox = gtk.VBox()
        frame.add(frameVBox)
        self.comboboxDebayer = gtk.combo_box_new_text()
        self.comboboxDebayer.append_text("None")
        self.comboboxDebayer.append_text("BGGR")
        self.comboboxDebayer.append_text("GRBG")
        self.comboboxDebayer.set_active(0)
        self.comboboxDebayer.connect("changed", self.combobox_debayer_changed)
        frameVBox.pack_start(self.comboboxDebayer,expand=False,fill=False,padding=0)
        vboxDemosaic.pack_start(frame,expand=False,fill=False,padding=0)

        frame = gtk.Frame("Score:")
        frameVBox = gtk.VBox()
        frame.add(frameVBox)
        vboxDemosaic.pack_start(frame, expand=False, fill=False, padding=0)
        self.comboboxScore = gtk.combo_box_new_text()
        frameVBox.pack_start(self.comboboxScore, expand=False, fill=False, padding=0)
        self.comboboxScore.append_text("None")
        self.comboboxScore.append_text("Entropy")
        self.comboboxScore.append_text("SNR")
        self.comboboxScore.append_text("Gradient")
        self.comboboxScore.append_text("Value*Gradient")
        self.comboboxScore.append_text("Std-deviation")
        self.comboboxScore.append_text("Correlation")
        self.comboboxScore.set_active(0)
        self.comboboxScore.connect("changed", self.combobox_score_changed)

        #---Create buttons to start and stop the image registration---
        vboxControls = gtk.VBox()
        frame = gtk.Frame("Registration:")
        frameVBox = gtk.VBox()
        frame.add(frameVBox)
        vboxControls.pack_start(frame, expand=False, fill=False, padding=0)
        self.comboboxMethod = gtk.combo_box_new_text()
        frameVBox.pack_start(self.comboboxMethod, expand=False, fill=False, padding=0)
        self.comboboxMethod.append_text("None")
        self.comboboxMethod.append_text("FFT correlation")
        self.comboboxMethod.append_text("Canny FFT")
        self.comboboxMethod.append_text("Geometric Median")
        self.comboboxMethod.set_active(0)
        self.comboboxMethod.connect("changed", self.combobox_method_changed)

        self.buttonRegister = gtk.Button("Process")
        self.buttonRegister.connect("clicked", self.buttonRegister_callback)
        self.buttonRegister.set_flags(gtk.CAN_DEFAULT)
        vboxControls.pack_start(self.buttonRegister,False,False,0)
        self.buttonStop = gtk.Button("Stop")
        self.buttonStop.connect("clicked", self.buttonStop_callback)
        self.buttonStop.set_sensitive(False)
        vboxControls.pack_start(self.buttonStop,False,False,0)

        hboxBottom.pack_start(vboxControls, expand=False, fill=False, padding=0)

        self.ui_switch(False)
        vbox.show_all()
        
        return vbox
